// Copyright (C) 2010 Razvan Constantin <razvan.constantin@rconst.net>
//
// This file is part of FileBow.
//
// FileBow is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// FileBow is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with FileBow.  If not, see <http://www.gnu.org/licenses/>.
//
// Implementation of TopBuilder.

#include "logic/top_builder.h"

#include <set>
#include <stack>
#include <string>
#include <vector>
#include <Poco/Path.h>

#include "logic/path-tree.h"
#include "logic/file_filter.h"
#include "logic/file-scorer.h"

using namespace std;

namespace filebow {

////////////////////////////////////////////////////////////////////////
// Top.
////////////////////////////////////////////////////////////////////////

class Top {
  public: Top(int size) : mSize(size), mEntries(EntryComp()) {}
  public: ~Top() {}
  public: void Insert(const PathTree::Node* node, int score);
  public: void Get(vector<string>* v) const;
  private: int mSize;
  private: struct Entry {
    const PathTree::Node* node;
    int score;
  };
  private: struct EntryComp {
    bool operator()(const Entry& l, const Entry& r) const;
  };
  private: typedef set<Entry, EntryComp> EntrySet;
  private: EntrySet mEntries;
};

void Top::Insert(const PathTree::Node* node, int score) {
  if (mEntries.size() >= mSize && score <= mEntries.begin()->score) {
    return;
  }
  Entry entry;
  entry.node = node;
  entry.score = score;
  mEntries.insert(entry);
  if (mEntries.size() > mSize) {
    mEntries.erase(mEntries.begin());
  }
}

void Top::Get(vector<string>* v) const {
  for(EntrySet::reverse_iterator it(mEntries.rbegin());
      it != mEntries.rend(); ++it) {
    string path = PathTree::GetPath(it->node).toString();
    v->push_back(path);
  }
}

bool Top::EntryComp::operator()(const Entry& l, const Entry& r) const {
  if (l.score < r.score) {
    return true;
  } else if (l.score > r.score) {
    return false;
  } else {
    return l.node < r.node;
  }
}

////////////////////////////////////////////////////////////////////////
// Walker.
////////////////////////////////////////////////////////////////////////

class Walker : public PathTree::Walker {
  public: Walker(const FileFilter& filter, Top* top);
  public: ~Walker() {}
  public: void SetScorer(const FileScorer& scorer) { mScorer = &scorer; }
  public: void EnterDir(const PathTree::Node* node);
  public: bool SkipDir();
  public: void ExitDir();
  public: void VisitFile(const PathTree::Node* node);
  private: struct State {
    const PathTree::Node* node;
    FileFilter* filter;
    int score;
  };
  private: const FileScorer* mScorer;
  private: stack<State> mStates;
  private: Top* mTop;
};

Walker::Walker(const FileFilter& filter, Top* top)
    : mScorer(NULL), mStates(), mTop(top) {
  CHECK_PTR(top);
  State state;
  state.node = NULL;
  state.filter = const_cast<FileFilter*>(&filter);
  state.score = 0;
  mStates.push(state);
}

void Walker::EnterDir(const PathTree::Node* node) {
  const State& oldState = mStates.top();
  State newState;
  newState.node = node;
  newState.filter = EnsurePartialFilter(*oldState.filter, *node);
  newState.score = oldState.score + mScorer->ScoreDir(node);
  mStates.push(newState);
}

bool Walker::SkipDir() {
  const State& state = mStates.top();
  if (!state.node || !state.filter) {
    return false;
  }
  return state.filter->Filter(*state.node) == EXCLUDE;
}

void Walker::ExitDir() {
  delete mStates.top().filter;
  mStates.pop();
}

void Walker::VisitFile(const PathTree::Node* node) {
  const State& state = mStates.top();
  auto_ptr<FileFilter> filter(EnsurePartialFilter(*state.filter, *node));
  if (filter->Filter(*node) == EXCLUDE) {
    return;
  }
  mTop->Insert(node, state.score + mScorer->ScoreFile(node));
}

////////////////////////////////////////////////////////////////////////
// TopBuilder.
////////////////////////////////////////////////////////////////////////

TopBuilder::TopBuilder(const FileFilter& filter, int size)
    : mTop(new Top(size)), mWalker(new Walker(filter, mTop.get())) {}

TopBuilder::~TopBuilder() {}

void TopBuilder::Visit(const FileScorer& scorer, PathTree* tree) {
  mWalker->SetScorer(scorer);
  PathTree::Iterator(tree, mWalker.get()).VisitAll();
}

void TopBuilder::Get(vector<string>* v) {
  mTop->Get(v);
}

} // namespace filebow
